using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Media3D;
using System.Windows.Markup;
using System.Windows.Documents;
using System.Windows.Data;
using System.Windows.Shapes;
using System.Collections;
using System.Collections.Generic;

namespace Sinacor.Infra.UI.Controls.SmartClient
{
    public class FlowPanel : LogicalPanel
    {
        private ContainerUIElement3D _modelContainer;
        private Viewport3D _viewport;
        private bool _hasReflection;

        public int SelectedIndex
		{
			get { return (int) GetValue(SelectedIndexProperty); }
			set { SetValue(SelectedIndexProperty, value); }
		}

		public double TiltAngle
		{
			get { return (double) GetValue(TiltAngleProperty); }
			set { SetValue(TiltAngleProperty, value); }
		}

		public double ItemGap
		{
			get { return (double) GetValue(ItemGapProperty); }
			set { SetValue(ItemGapProperty, value); }
		}

		public double FrontItemGap
		{
			get { return (double) GetValue(FrontItemGapProperty); }
			set { SetValue(FrontItemGapProperty, value); }
		}

		public double PopoutDistance
		{
			get { return (double) GetValue(PopoutDistanceProperty); }
			set { SetValue(PopoutDistanceProperty, value); }
		}

		public ViewStateBase CurrentView
		{
			get { return (ViewStateBase) GetValue(CurrentViewProperty); }
			set { SetValue(CurrentViewProperty, value); }
		}

		public double ElementWidth
		{
			get { return (double) GetValue(ElementWidthProperty); }
			set { SetValue(ElementWidthProperty, value); }
		}

		public double ElementHeight
		{
			get { return (double) GetValue(ElementHeightProperty); }
			set { SetValue(ElementHeightProperty, value); }
		}

		public PerspectiveCamera Camera
		{
            get { return (PerspectiveCamera)GetValue(CameraProperty); }
			set { SetValue(CameraProperty, value); }
		}

		internal int VisibleChildrenCount
		{
			get { return _modelContainer.Children.Count; }
		}

		public bool HasReflection 
        {
            get { return _hasReflection; }
            set { _hasReflection = value; } 
        }

        public static readonly DependencyProperty CameraProperty = DependencyProperty.Register("Camera", typeof(PerspectiveCamera), typeof(FlowPanel), new FrameworkPropertyMetadata(new PerspectiveCamera()
        {
            FieldOfView = 90,
            LookDirection = new Vector3D(0, -1, -1),
            Position = new Point3D(0, 1, 1),
            UpDirection = new Vector3D(0, 1, 0),
            FarPlaneDistance = 5,
            NearPlaneDistance = 0.125
        }, OnCameraChanged));

		public static readonly DependencyProperty CurrentViewProperty = DependencyProperty.Register("CurrentView", typeof (ViewStateBase), typeof (FlowPanel), new FrameworkPropertyMetadata(null, OnCurrentViewChanged));

		public static readonly DependencyProperty ElementHeightProperty = DependencyProperty.Register("ElementHeight", typeof (double), typeof (FlowPanel), new FrameworkPropertyMetadata(100.0));

		public static readonly DependencyProperty ElementWidthProperty = DependencyProperty.Register("ElementWidth", typeof (double), typeof (FlowPanel), new FrameworkPropertyMetadata(170.0));

		public static readonly DependencyProperty FrontItemGapProperty = DependencyProperty.Register("FrontItemGap", typeof (double), typeof (FlowPanel), new FrameworkPropertyMetadata(0.65, OnFrontItemGapChanged));

		public static readonly DependencyProperty HasReflectionProperty = DependencyProperty.Register("HasReflection", typeof (bool), typeof (FlowPanel), new FrameworkPropertyMetadata(false));

		public static readonly DependencyProperty ItemGapProperty = DependencyProperty.Register("ItemGap", typeof (double), typeof (FlowPanel), new FrameworkPropertyMetadata(0.25, OnItemGapChanged));

		private static readonly DependencyProperty LinkedElementProperty = DependencyProperty.Register("LinkedElement", typeof (UIElement), typeof (FlowPanel)); 

		private static readonly DependencyProperty LinkedModelProperty = DependencyProperty.Register("LinkedModel", typeof (ModelUIElement3D), typeof (FlowPanel));

		public static readonly DependencyProperty PopoutDistanceProperty = DependencyProperty.Register("PopoutDistance", typeof (double), typeof (FlowPanel), new FrameworkPropertyMetadata(1.0, OnPopoutDistanceChanged));

		public static readonly DependencyProperty SelectedIndexProperty = DependencyProperty.Register("SelectedIndex", typeof (int), typeof (FlowPanel), new FrameworkPropertyMetadata(-1, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedIndexChanged));

		public static readonly DependencyProperty TiltAngleProperty = DependencyProperty.Register("TiltAngle", typeof (double), typeof (FlowPanel), new FrameworkPropertyMetadata(45.0, OnTiltAngleChanged));

		public FlowPanel()
		{
			this.LoadViewport();
			this.SetupEventHandlers();
            CurrentView = new VForm();
		}

		private void SetupEventHandlers()
		{
			this._modelContainer.MouseLeftButtonDown += OnContainerLeftButtonDown;
			Loaded += FlowPanel_Loaded;
		}

		private void LoadViewport()
		{
            this._viewport = CreateViewport();
            this. _modelContainer = _viewport.Children[0] as ContainerUIElement3D;
		}

        private Viewport3D CreateViewport()
        {
            Viewport3D viewport = new Viewport3D
            {
                Camera = new PerspectiveCamera
                {
                    FieldOfView = 90,
                    LookDirection = new Vector3D(0, -1, -1),
                    Position = new Point3D(0, 1, 1),
                    UpDirection = new Vector3D(0, 1, 0),
                    FarPlaneDistance = 5,
                    NearPlaneDistance = 0.125                    
                },
                ClipToBounds = true,
                SnapsToDevicePixels = true,
                FlowDirection = FlowDirection.LeftToRight
            };

            viewport.Children.Add(new ContainerUIElement3D());

            viewport.Children.Add(new ModelVisual3D
            {
                Content = new AmbientLight()
                {
                    Color = Colors.White
                }
            });            

            return viewport;
        }

		private static void OnTiltAngleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			FlowPanel cf = d as FlowPanel;
			cf.ReflowItems();
		}

		private static void OnItemGapChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			FlowPanel ef = d as FlowPanel;
			ef.ReflowItems();
		}

		private static void OnFrontItemGapChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			FlowPanel ef = d as FlowPanel;
			ef.ReflowItems();
		}

		private static void OnPopoutDistanceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			FlowPanel ef = d as FlowPanel;
			ef.ReflowItems();
		}

		private static void OnSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			FlowPanel ef = d as FlowPanel;
			if (ef.IsLoaded == false)
			{
				return;
			}
			ef.SelectItemCore((int) e.NewValue);
		}

		private static void OnCurrentViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			FlowPanel ef = d as FlowPanel;
			ViewStateBase newView = e.NewValue as ViewStateBase;
			if (newView == null)
			{
				throw new ArgumentNullException("e", "The CurrentView cannot be null");
			}
			newView.SetOwner(ef);
			ef.ReflowItems();
		}

		private static void OnCameraChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			FlowPanel ef = d as FlowPanel;
            PerspectiveCamera camera = e.NewValue as PerspectiveCamera;
			if (camera == null)
			{
				throw new ArgumentNullException("e", "The Camera cannot be null");
			}
			ef._viewport.Camera = camera;
		}

		protected override void OnInitialized(EventArgs e)
		{
			AddVisualChild(_viewport);
		}

		private void FlowPanel_Loaded(object sender, RoutedEventArgs e)
		{
			SelectItemCore(SelectedIndex);
		}

		private void OnContainerLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			Focus();
			Viewport2DVisual3D model = e.Source as Viewport2DVisual3D;
			if (model != null)
			{
				SelectedIndex = _modelContainer.Children.IndexOf(model);
			}                  
		}

		protected override void OnMouseWheel(MouseWheelEventArgs e)
		{
			Focus();
			if (e.Delta < 0)
				SelectAdjacentItem(false);
			else if (e.Delta > 0)
				SelectAdjacentItem(true);
		}

		protected override void OnKeyDown(KeyEventArgs e)
		{
			if (e.Key == Key.Left)
				SelectAdjacentItem(false);
			else if (e.Key == Key.Right)
				SelectAdjacentItem(true);
		}

		private void SelectItemCore(int index)
		{
			if (index >= 0 && index < VisibleChildrenCount)
				CurrentView.SelectElement(index);
		}

		internal Storyboard PrepareTemplateStoryboard(int index)
		{
            Storyboard sb = CreateStoryboard().Clone();
			Rotation3DAnimation rotAnim = sb.Children[0] as Rotation3DAnimation;
			Storyboard.SetTargetProperty(rotAnim, BuildTargetPropertyPath(index, "Rotation"));

			DoubleAnimation xAnim = sb.Children[1] as DoubleAnimation;
			Storyboard.SetTargetProperty(xAnim, BuildTargetPropertyPath(index, "Translation-X"));

			DoubleAnimation yAnim = sb.Children[2] as DoubleAnimation;
			Storyboard.SetTargetProperty(yAnim, BuildTargetPropertyPath(index, "Translation-Y"));

			DoubleAnimation zAnim = sb.Children[3] as DoubleAnimation;
			Storyboard.SetTargetProperty(zAnim, BuildTargetPropertyPath(index, "Translation-Z"));

            return sb;
		}

        private Storyboard CreateStoryboard()
        {
            Storyboard storyboard = new Storyboard();
            storyboard.Children.Add(new Rotation3DAnimation()
            {
                DecelerationRatio = 0.5,
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                To = new AxisAngleRotation3D() 
                {
                    Axis = new Vector3D(0,1,0),
                    Angle = 0.0
                }
            });

            storyboard.Children.Add(new DoubleAnimation()
            {
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                DecelerationRatio = 0.5,
                To = 0.0
            });

            storyboard.Children.Add(new DoubleAnimation()
            {
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                DecelerationRatio = 0.5,
                To = 0.0
            });

            storyboard.Children.Add(new DoubleAnimation()
            {
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                DecelerationRatio = 0.5,
                To = 0.0
            });
            return storyboard;
        }

		private PropertyPath BuildTargetPropertyPath(int index, string animType)
		{
			PropertyDescriptor childDesc = TypeDescriptor.GetProperties(_modelContainer).Find("Children", true);
			string pathString = string.Empty;
			if (animType == "Rotation")
				pathString = "(0)[0].(1)[" + index + "].(2).(3)[0].(4)";
			else if (animType == "Translation-X")
				pathString = "(0)[0].(1)[" + index + "].(2).(3)[1].(5)";
			else if (animType == "Translation-Y")
				pathString = "(0)[0].(1)[" + index + "].(2).(3)[1].(6)";
			else if (animType == "Translation-Z")
				pathString = "(0)[0].(1)[" + index + "].(2).(3)[1].(7)";

            return new PropertyPath(pathString,
                                    Viewport3D.ChildrenProperty,
			                        childDesc,
			                        Viewport2DVisual3D.TransformProperty,
			                        Transform3DGroup.ChildrenProperty,
			                        RotateTransform3D.RotationProperty,
			                        TranslateTransform3D.OffsetXProperty,
			                        TranslateTransform3D.OffsetYProperty,
			                        TranslateTransform3D.OffsetZProperty);
		}

		internal void AnimateElement(Storyboard sb)
		{
			sb.Begin(this._viewport, true);
		}

		protected override int VisualChildrenCount
		{
			get
			{
				int count = base.VisualChildrenCount;
				count = (count == 0) ? 0 : 1;
				return count;
			}
		}

		protected override Size ArrangeOverride(Size finalSize)
		{
			Size eltSize = new Size(ElementWidth, ElementHeight);
			foreach (UIElement child in Children)
				child.Arrange(new Rect(new Point(), eltSize));
			_viewport.Arrange(new Rect(new Point(), finalSize));
			return finalSize;
		}

		protected override Size MeasureOverride(Size availableSize)
		{
			Size eltSize = new Size(ElementWidth, ElementHeight);
			foreach (UIElement child in Children)
				child.Measure(eltSize);
			_viewport.Measure(availableSize);
			return availableSize;
		}

		protected override Visual GetVisualChild(int index)
		{
			if (index == 0)
				return _viewport;
			return null;
		}

        protected override void OnLogicalChildrenChanged(UIElement childAdded, UIElement childRemoved)
		{
			base.OnVisualChildrenChanged(childAdded, childRemoved);
			if (childAdded != null)
				OnVisualAdded(childAdded as UIElement);

			if (childRemoved != null)
                OnVisualRemoved(childRemoved as UIElement);
		}

		private void ReflowItems()
		{
			SelectItemCore(SelectedIndex);
		}

		private void SelectAdjacentItem(bool isNext)
		{
			int index = -1;
			if (isNext == false)
				index = Math.Max(-1, SelectedIndex - 1);
			else
				index = Math.Min(VisibleChildrenCount - 1, SelectedIndex + 1);

			if (index != -1)
				SelectedIndex = index;
		}

		private void OnVisualRemoved(UIElement elt)
		{
            Viewport2DVisual3D model = elt.GetValue(LinkedModelProperty) as Viewport2DVisual3D;
			_modelContainer.Children.Remove(model);

			model.ClearValue(LinkedElementProperty);
			elt.ClearValue(LinkedModelProperty);

			if (SelectedIndex >= 0 && SelectedIndex < VisibleChildrenCount)
				ReflowItems();
			else
				SelectedIndex = Math.Max(0, Math.Min(SelectedIndex, VisibleChildrenCount - 1));
		}

        private void OnVisualAdded(UIElement elt)
		{
            if (elt is Viewport3D) return;

			int index = Children.IndexOf(elt);
            elt.SnapsToDevicePixels = true;

            Viewport2DVisual3D view = this.CreateViewport2DVisual3D(elt);
            this._modelContainer.Children.Insert(index, view);           

			if (IsLoaded)
				ReflowItems();
		}

        private Viewport2DVisual3D CreateViewport2DVisual3D(UIElement elt)
        {
            Viewport2DVisual3D model = new Viewport2DVisual3D
            {
                Geometry = new MeshGeometry3D
                {
                    TriangleIndices = new Int32Collection(
                        new int[]
                        {
                            0, 3, 2, 0, 2,1
                        }),
                    TextureCoordinates = new PointCollection(
                        new Point[] 
                        { 
                            new Point(0, 0), 
                            new Point(1, 0), 
                            new Point(1, 1), 
                            new Point(0, 1) 
                        }),
                    Positions = CreateMeshPositions()
                },
                Material = new DiffuseMaterial(),
                Transform = CreateTransform3DGroup(),
                Visual = elt
            };
                        
            Viewport2DVisual3D.SetIsVisualHostMaterial(model.Material, true);
            return model;
        }

        private Transform3DGroup CreateTransform3DGroup()
        {
            Transform3DGroup group = new Transform3DGroup();
            group.Children.Add(new RotateTransform3D() 
            {
                Rotation = new AxisAngleRotation3D()
                {
                    Angle = 0,
                    Axis = new Vector3D(0, 1, 0)
                }
            });

            group.Children.Add(new TranslateTransform3D()
            {
                OffsetX = 0,
                OffsetY = 0,
                OffsetZ = 0
            });

            group.Children.Add(new ScaleTransform3D()
            {
                CenterX = 0,
                CenterY = 0,
                CenterZ = 0,
                ScaleX = 1,
                ScaleY = 1,
                ScaleZ = 1
            });
            return group;
        }

        private Point3DCollection CreateMeshPositions()
        {
            double eltHeight = ElementHeight / 100;
            double eltWidth = ElementWidth / 100;

            Point3DCollection positions = new Point3DCollection();

            positions.Add(new Point3D(-eltWidth, eltHeight, 0));
            positions.Add(new Point3D(eltWidth, eltHeight, 0));
            positions.Add(new Point3D(eltWidth, -eltHeight, 0));
            positions.Add(new Point3D(-eltWidth, -eltHeight, 0));

            return positions;
        }
    }
}